using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using FireStarter.Base;
using FireStarterModeller.BO;
using Habanero.Base;
using Habanero.Base.Exceptions;
using Habanero.BO;

namespace FireStarter.CodeGeneration
{
    public interface IMasterGenerator : IGenerator
    {
        void SetupGenerator(DMSolution dmSolution);
        string ClassDefsXml { get; set; }
    }

    /// <summary>
    /// Directs the entire generation process, including generating
    /// solutions, projects and code files
    /// </summary>
    public class MasterGenerator : IMasterGenerator
    {
        private DMSolution DMSolution { get; set; }
        public List<IProjectGenerator> ProjectGenerators { get; set; }
        public string ClassDefsXml { get; set; }
        public ISolutionGenerator SolutionGenerator { get; set; }

        public void SetupGenerator(DMSolution dmSolution)
        {
            DMSolution = dmSolution;
            if (dmSolution == null) throw new ArgumentNullException("dmSolution");
            GenerationSettings generationSettings = dmSolution.GenerationSettings;
            if (generationSettings == null)
                throw new PropertyMissingException("GenerationSettings", "Generation Settings cannot be null");
            if (generationSettings.SolutionName == null)
                throw new PropertyMissingException("SolutionName", "Solution name must be set.");
            if (generationSettings.CodeTemplateLibraryName == null)
                throw new PropertyMissingException("CodeTemplateLibraryName", "Code Template Library Name must be set.");
            if (generationSettings.SolutionType == null)
                throw new PropertyMissingException("SolutionType", "Solution Type must be set.");

            SetupSolutionGenerator();

            ProjectGenerators = new List<IProjectGenerator>();
            BusinessObjectCollection<ProjectTypeGeneration> projectTypeGenerations =
                GetOrderedProjectTypeGenerations(generationSettings.ProjectTypeGenerations);

            string boProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_BO);
            string logicProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_LOGIC);
            string uiBaseProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_UI_BASE);
            string uiVWGProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_UI_VWG);
            string uiWinProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_UI_WIN);
            string boTestProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_TEST_BO);
            string baseTestProjectName = GetProjectName(projectTypeGenerations, GenerationTypes.PROJECT_TYPE_TEST_BASE);

            dmSolution.BOProjectName = boProjectName;
            dmSolution.LogicProjectName = logicProjectName;
            dmSolution.UIProjectName = uiBaseProjectName;
            dmSolution.UIVWGProjectName = uiVWGProjectName;
            dmSolution.UIWinProjectName = uiWinProjectName;
            dmSolution.BOTestProjectName = boTestProjectName;
            dmSolution.BaseTestProjectName = baseTestProjectName;

            string solutionDirectory = GetAbsoluteSolutionCodeDirectory();
            foreach (ProjectTypeGeneration projectTypeGeneration in projectTypeGenerations)
            {
                if (projectTypeGeneration.ToBeGenerated.Value)
                {
                    string projectTypeName = projectTypeGeneration.ProjectType.Name;
                    string projectName = projectTypeGeneration.ProjectName;

                    IProjectGenerator projectGenerator = GetProjectGenerator(projectTypeName, DMSolution);
                    projectGenerator.SolutionGenerator = SolutionGenerator;
                    projectGenerator.ProjectName = projectName;
                    projectGenerator.ProjectPath = Path.Combine(solutionDirectory, projectName);
                    ProjectGenerators.Add(projectGenerator);

                    SolutionGenerator.ProjectGenerators.Add(projectGenerator);
                }
            }
        }

        private string GetAbsoluteSolutionCodeDirectory()
        {
            return DMSolution.GetAbsoutePath(DMSolution.GenerationSettings.SolutionDirectory);
            //string solutionDirectory = DMSolution.GenerationSettings.SolutionDirectory;
            //if (solutionDirectory == null)
            //{
            //    solutionDirectory = DMSolution.SolutionDirectory;
            //}
            //if (!Path.IsPathRooted(solutionDirectory))
            //{
            //    solutionDirectory = Path.Combine(DMSolution.SolutionDirectory, solutionDirectory);
            //}
            //return solutionDirectory;
        }

        private void SetupSolutionGenerator()
        {
            GenerationSettings generationSettings = DMSolution.GenerationSettings;

            SolutionGenerator = new SolutionGenerator
                                    {
                                        SolutionName = generationSettings.SolutionName,
                                        HabaneroReferencePath = DMSolution.GetAbsoutePath(generationSettings.HabaneroDllPath),
                                        SolutionTypeName = generationSettings.SolutionType.Name,
                                        ModelSolution = DMSolution,
                                        ModelClasses = ((IModelSolution) DMSolution).ModelClasses,
                                        ClassDefsString = ClassDefsXml,
                                        CodeTemplate = generationSettings.CodeTemplateLibraryName.GetValueOrDefault()
                                    };
            SolutionGenerator.ModelSolution.ApplicationName = generationSettings.SolutionName;

            SolutionGenerator.SolutionDirectory = DMSolution.GetAbsoutePath(generationSettings.SolutionDirectory);


        }

        /// <summary>
        /// Need to create projects in hierarchical order, so that the project Guids
        /// don't get recreated (BO is top of the hierarchy)
        /// </summary>
        protected static BusinessObjectCollection<ProjectTypeGeneration> GetOrderedProjectTypeGenerations(BusinessObjectCollection<ProjectTypeGeneration> projectTypeGenerations)
        {
            BusinessObjectCollection<ProjectTypeGeneration> outputCol = new BusinessObjectCollection<ProjectTypeGeneration>();
            BusinessObjectCollection<ProjectTypeGeneration> clonedInputCol = projectTypeGenerations.Clone();

            List<string> projectTypeHierarchy = new List<string>();
            string[] typeGenerations = new[]
            {
                GenerationTypes.PROJECT_TYPE_BO,
                GenerationTypes.PROJECT_TYPE_LOGIC,
                GenerationTypes.PROJECT_TYPE_UI_BASE,
                GenerationTypes.PROJECT_TYPE_UI_VWG,
                GenerationTypes.PROJECT_TYPE_UI_WIN,
                GenerationTypes.PROJECT_TYPE_MAIN_VWG,
                GenerationTypes.PROJECT_TYPE_MAIN_WIN,
                GenerationTypes.PROJECT_TYPE_TEST_BASE,
                GenerationTypes.PROJECT_TYPE_TEST_BO,
                GenerationTypes.PROJECT_TYPE_TEST_LOGIC,
                GenerationTypes.PROJECT_TYPE_TEST_UI
            };
            foreach (string genType in typeGenerations)
            {
                projectTypeHierarchy.Add(genType);
            }

            foreach (string projectTypeName in projectTypeHierarchy)
            {
                ProjectTypeGeneration projectTypeGeneration = FindMatchingProjectTypeGeneration(clonedInputCol, projectTypeName, outputCol);
                if (projectTypeGeneration != null)
                {
                    outputCol.Add(projectTypeGeneration);
                    clonedInputCol.Remove(projectTypeGeneration);
                }
            }

            foreach (ProjectTypeGeneration projectTypeGeneration in clonedInputCol)
            {
                outputCol.Add(projectTypeGeneration);
            }

            return outputCol;
        }

        private static ProjectTypeGeneration FindMatchingProjectTypeGeneration(BusinessObjectCollection<ProjectTypeGeneration> projectTypeGenerations, string projectTypeName, BusinessObjectCollection<ProjectTypeGeneration> outputCol)
        {
            foreach (ProjectTypeGeneration projectTypeGeneration in projectTypeGenerations)
            {
                ProjectType projectType = projectTypeGeneration.ProjectType;
                if (projectType != null && projectType.Name == projectTypeName)
                {
                    return projectTypeGeneration;
                }
            }
            return null;
        }

        private static string GetProjectName(BusinessObjectCollection<ProjectTypeGeneration> projectTypeGenerations, string projectType)
        {
            ProjectTypeGeneration projectTypeGeneration =
                projectTypeGenerations.Find(
                    aProjectTypeGeneration =>
                    aProjectTypeGeneration.ProjectType.Name == projectType);
            return (projectTypeGeneration != null)
                       ? projectTypeGeneration.ProjectName
                       : null;
        }

        private static bool HasAnyErrors(IList<IBOError> errors)
        {
            return errors.Count != 0 && new List<IBOError>(errors).Exists(t => t.Level == ErrorLevel.Error);
        }

        public Result Generate()
        {
            if (DMSolution == null)
            {
                throw new PropertyMissingException("DMSolution has not been set, probably because SetupGenerator has not been called first");
            }

            //    if (Global.ErrorMessages.Count > 0)
            //    {
            //        errors = "There are currently errors listed for this project. " +
            //                 "Please resolve these errors before generating. " +
            //                 "The 'Error List' can be opened from the 'View' menu.";
            //        return false;
            //    }
            IList<IBOError> errors = DMSolution.Validate();

            if (HasAnyErrors(errors))
            {
                StringBuilder stringBuilder = new StringBuilder("Validation of the domain model failed. Please" +
                                         Environment.NewLine + "correct the errors before generating code.");
                stringBuilder.AppendLine();
                stringBuilder.AppendLine();
                foreach (IBOError error in errors)
                {
                    stringBuilder.AppendLine(string.Format("{0}: {1}", error.Level, error.Message));
                }

                return new Result(false, stringBuilder.ToString());
            }

            foreach (IProjectGenerator projectGenerator in ProjectGenerators)
            {
                projectGenerator.Generate();
            }

            SolutionGenerator.Generate(DMSolution.GenerationSettings.CodeTemplateLibraryName.Value);

            UpdateClassesNeedsRegenerationFlag();

            return new Result(true);
        }

        private void UpdateClassesNeedsRegenerationFlag()
        {
            foreach (IModelClass modelClass in SolutionGenerator.ModelClasses)
            {
                if (modelClass.GenerateCode) modelClass.NeedsRegeneration = false;
            }
        }

        public static IProjectGenerator GetProjectGenerator(string projectTypeName, DMSolution dmSolution)
        {
            switch (projectTypeName)
            {
                case GenerationTypes.PROJECT_TYPE_BO:
                    return new BoProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_LOGIC:
                    return new LogicProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_UI_VWG:
                    return new UIVWGProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_UI_WIN:
                    return new UIWinProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_UI_BASE:
                    return new UIBaseProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_MAIN_WIN:
                    return new WinProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_MAIN_VWG:
                    return new VWGProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_TEST_BO:
                    return new BOTestProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_TEST_LOGIC:
                    return new LogicTestProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_TEST_BASE:
                    return new BaseTestProjectGenerator();
                case GenerationTypes.PROJECT_TYPE_TEST_UI:
                    return new UITestProjectGenerator();
                default:
                    throw new HabaneroDeveloperException("Code generation does not yet support the project type: " + projectTypeName, "");
            }
        }

        public static string GetSuitableProjectName(string solutionName, string projectTypeName,
            string solutionTypeName)
        {
            if (solutionTypeName == GenerationTypes.SOLUTION_TYPE_VWG &&
                projectTypeName == GenerationTypes.PROJECT_TYPE_UI_VWG)
            {
                return solutionName + ".UI";
            }
            if (solutionTypeName == GenerationTypes.SOLUTION_TYPE_WIN &&
                projectTypeName == GenerationTypes.PROJECT_TYPE_UI_WIN)
            {
                return solutionName + ".UI";
            }
            if (solutionTypeName == GenerationTypes.SOLUTION_TYPE_VWG &&
                projectTypeName == GenerationTypes.PROJECT_TYPE_MAIN_VWG)
            {
                return solutionName;
            }
            if (solutionTypeName == GenerationTypes.SOLUTION_TYPE_WIN &&
                projectTypeName == GenerationTypes.PROJECT_TYPE_MAIN_WIN)
            {
                return solutionName;
            }
            if (solutionTypeName == GenerationTypes.SOLUTION_TYPE_DUALWINVWG &&
                projectTypeName == GenerationTypes.PROJECT_TYPE_MAIN_WIN)
            {
                return solutionName + ".Win";
            }
            if (solutionTypeName == GenerationTypes.SOLUTION_TYPE_DUALWINVWG &&
                projectTypeName == GenerationTypes.PROJECT_TYPE_MAIN_VWG)
            {
                return solutionName + ".VWG";
            }
            return solutionName + "." + projectTypeName;
        }
    }

}